﻿#region Usings

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Windows.Media.Imaging;

using Gonte.Reflection;
using Gonte.UI; 

#endregion

namespace Gonte.Wpf.Controls.ViewModel
{
    public class ObjectViewModel
        : ViewModelBase
    {
        #region Constants

        private const String NoChildrenProperties = "There are no children properties to select";
        private const String ToManyChildrenProperties = "There are no children properties to select. Use the method that with the property name parameter"; 

        #endregion

        #region Constructors

        public ObjectViewModel(ViewModelBase parent)
            : base(parent, false)
        {
        }

        public ObjectViewModel(ViewModelBase parent, object model)
            : base(parent, false)
        {
            PopulateViewModelFromModel(model);
        } 

        #endregion

        #region Properties

        /// <summary>
        /// The model this view model maps to
        /// </summary>
        public override object Model
        {
            get { return _model; }

            set 
            {
                PopulateViewModelFromModel(value);
            }
        }

        /// <summary>
        /// The label that identifies the model
        /// </summary>
        public override String Label
        {
            get
            {
                return label_;
            }

            set
            {
                if (_model != null)
                {
                    Type type = _model.GetType();
                    TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(type);
                    String existingValue = (String)typeAccessor.GetValue(_model, labelProperty_);

                    if (!value.Equals(existingValue))
                    {
                        label_ = value;
                        typeAccessor.SetValue(_model, labelProperty_, value);
                        OnPropertyChanged("Label");
                    }
                }
            }
        }

        public SetContextMenuHandler SetContextMenu
        {
            get { return setContextMenu_; }
            set { setContextMenu_ = value; }
        }

        #endregion

        #region Methods

        public bool AddChild(object child)
        {
            ExceptionThrower.ThrowWhen(childrenProperties_ == null, NoChildrenProperties);
            ExceptionThrower.ThrowWhen(childrenProperties_.Count > 1, ToManyChildrenProperties);

            String childrenProperty = childrenProperties_[0];
            return AddChild(childrenProperty, child);
        }

        /// <summary>
        /// Adds a child to the model with more than one children properties
        /// </summary>
        /// <param name="childrenProperty"></param>
        /// <param name="child"></param>
        /// <returns></returns>
        public bool AddChild(String childrenProperty, object child)
        {
            ExceptionThrower.ThrowWhen(childrenProperties_ == null, NoChildrenProperties);

            ObjectViewModel viewModel = new ObjectViewModel(this, child);

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(_model.GetType());
            object @object = typeAccessor.GetValue(_model, childrenProperty);
            IEnumerable collection = @object as IEnumerable;

            if (collection != null) // It is a collection
            {
                CollectionHelper.AddItem(_model, childrenProperty, collection, child);
            }
            else // It is either a null value of a single object. Note: Any existing object will be replaced
            {
                typeAccessor.SetValue(_model, childrenProperty, child);
            }

            return base.AddChild(viewModel);
        }

        public bool RemoveChild(object child)
        {
            ExceptionThrower.ThrowWhen(childrenProperties_ == null, NoChildrenProperties);
            ExceptionThrower.ThrowWhen(childrenProperties_.Count > 1, ToManyChildrenProperties);

            String childrenProperty = childrenProperties_[0];
            return RemoveChild(childrenProperty, child);
        }

        public bool RemoveChild(String childrenProperty, object child)
        {
            ExceptionThrower.ThrowWhen(childrenProperties_ == null, NoChildrenProperties);

            ViewModelBase viewModel = FindChild(child);

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(_model.GetType());
            object @object = typeAccessor.GetValue(_model, childrenProperty);
            IEnumerable collection = @object as IEnumerable;

            if (collection != null) // It is a collection
            {
                CollectionHelper.RemoveItem(_model, childrenProperty, collection, child);
            }
            else // It is either a null value of a single object. Note: Any existing object will be set to null
            {
                typeAccessor.SetValue(_model, childrenProperty, null);
            }

            return base.RemoveChild(viewModel);
        }

        public bool MoveChildUp(object child)
        {
            ExceptionThrower.ThrowWhen(childrenProperties_ == null, NoChildrenProperties);
            ExceptionThrower.ThrowWhen(childrenProperties_.Count > 1, ToManyChildrenProperties);

            String childrenProperty = childrenProperties_[0];
            return MoveChildUp(childrenProperty, child);
        }

        private bool MoveChildUp(String childrenProperty, object child)
        {
            ExceptionThrower.ThrowWhen(childrenProperties_ == null, NoChildrenProperties);

            ViewModelBase viewModel = FindChild(child);
            
            int newIndex;
            if (!base.MoveChildUp(viewModel, out newIndex))
            {
                return false;
            }

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(_model.GetType());
            object @object = typeAccessor.GetValue(_model, childrenProperty);
            IEnumerable collection = @object as IEnumerable;

            if (collection != null) // It is a collection
            {
                CollectionHelper.SwapItems(collection, newIndex + 1, newIndex);
                return true;
            }
            
            //It is either a null value or a single object. Nothing to move
            return false;
        }

        public bool MoveChildDown(object child)
        {
            ExceptionThrower.ThrowWhen(childrenProperties_ == null, NoChildrenProperties);
            ExceptionThrower.ThrowWhen(childrenProperties_.Count > 1, ToManyChildrenProperties);

            String childrenProperty = childrenProperties_[0];
            return MoveChildDown(childrenProperty, child);
        }

        public bool MoveChildDown(String childrenProperty, object child)
        {
            ExceptionThrower.ThrowWhen(childrenProperties_ == null, NoChildrenProperties);

            ViewModelBase viewModel = FindChild(child);

            int newIndex;
            if (!base.MoveChildDown(viewModel, out newIndex))
            {
                return false;
            }

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(_model.GetType());
            object @object = typeAccessor.GetValue(_model, childrenProperty);
            IEnumerable collection = @object as IEnumerable;

            if (collection != null) // It is a collection
            {
                CollectionHelper.SwapItems(collection, newIndex - 1, newIndex);
                return true;
            }

            //It is either a null value or a single object. Nothing to move
            return false;
        }

        #endregion

        #region Overridables

        public override ContextMenu CreateContextMenu()
        {
            ViewModelInfo viewModelInfo = ViewModelInfo.GetInfo(_model.GetType());

            if (viewModelInfo.ContextMenuFactory != null)
            {
                return viewModelInfo.ContextMenuFactory(_model);
            }

            return null;
        }

        public override void OnPropertyChanged(String propertyName, object oldValue)
        {
            if (propertyName.Equals(labelProperty_))
            {
                base.OnPropertyChanged("Label");
            }
        }

        public override int CompareTo(object @object)
        {
            throw new NotImplementedException();
        } 

        #endregion

        #region Helpers

        ///// <summary>
        ///// Creates the view model info from the type
        ///// </summary>
        ///// <param name="object"></param>
        //private static void CreateViewModelInfo(Type type)
        //{
        //    if (ViewModelInfo.HasInfo(type))
        //    {
        //        return;
        //    }

        //    UserInterfaceAttribute viewModelAttribute = CustomAttribute.GetCustomAttribute<UserInterfaceAttribute>(type, false);

        //    ExceptionThrower.ThrowWhenNull("viewModelAttribute", viewModelAttribute);
        //    ExceptionThrower.ThrowWhenNullOrEmpty("viewModelAttribute.LabelProperty", viewModelAttribute.LabelProperty);

        //    ViewModelInfo viewModelMetadata = ViewModelInfo.GetInfo(type);

        //    viewModelMetadata.LabelProperty = viewModelAttribute.LabelProperty;
        //    viewModelMetadata.IconUri = viewModelAttribute.IconUri;
        //    viewModelMetadata.CollapsedIconUri = viewModelAttribute.CollapsedIconUri;
        //    viewModelMetadata.ExpandedIconUri = viewModelAttribute.ExpandedIconUri;

        //    UserInterfaceItemAttribute[] childrenAttributes = CustomAttribute.GetCustomAttributes<UserInterfaceItemAttribute>(type, false);

        //    if (childrenAttributes != null)
        //    {
        //        List<string> childrenProperties = new List<string>();

        //        foreach (UserInterfaceItemAttribute childrenAttribute in childrenAttributes)
        //        {
        //            if (childrenAttribute != null
        //                && !string.IsNullOrEmpty(childrenAttribute.PropertyName))
        //            {
        //                childrenProperties.Add(childrenAttribute.PropertyName);
        //            }
        //        }

        //        viewModelMetadata.ChildrenProperties = new string[childrenProperties.Count];
        //        childrenProperties.CopyTo(viewModelMetadata.ChildrenProperties);
        //    }
        //}

        private void PopulateViewModelFromModel(object model)
        {
            if (model == null)
            {
                return;
            }

            _model = model;
            Type type = _model.GetType();

            ViewModelInfo viewModelInfo = ViewModelInfo.GetInfo(type);

            UserInterfaceAttribute viewModelAttribute = CustomAttribute.GetCustomAttribute<UserInterfaceAttribute>(type, false);
            ExceptionThrower.ThrowWhenNull("viewModelAttribute", viewModelAttribute);

            TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(type);

            // Set the label
            labelProperty_ = viewModelAttribute.LabelProperty;
            ExceptionThrower.ThrowWhenNullOrEmpty("viewModelAttribute.LabelProperty", labelProperty_);
            label_ = (String)typeAccessor.GetValue(_model, labelProperty_);

            //// Set the image
            //if (!String.IsNullOrEmpty(viewModelAttribute.CollapsedImageUri))
            //{
            //    IconSource = new BitmapImage(new Uri(viewModelAttribute.CollapsedImageUri));
            //}

            PopulateChildrenFromModel();
        }

        private void PopulateChildrenFromModel()
        {
            Type type = _model.GetType();
            UserInterfaceItemAttribute[] childrenAttributes = CustomAttribute.GetCustomAttributes<UserInterfaceItemAttribute>(type, false);

            if (childrenAttributes == null)
            {
                return;
            }

            childrenProperties_ = new List<String>();

            foreach (UserInterfaceItemAttribute childrenAttribute in childrenAttributes)
            {
                if (childrenAttribute != null
                    && !String.IsNullOrEmpty(childrenAttribute.PropertyName))
                {
                    childrenProperties_.Add(childrenAttribute.PropertyName);

                    TypeAccessor typeAccessor = TypeAccessor.GetTypeAccessor(type);
                    object child = typeAccessor.GetValue(_model, childrenAttribute.PropertyName);
                    IEnumerable collection = child as IEnumerable;

                    if (collection != null) // It is a collection
                    {
                        foreach (object item in collection)
                        {
                            Children.Add(new ObjectViewModel(this, item));
                        }
                    }
                    else // It is either a null value of a single object
                    {
                        Children.Add(new ObjectViewModel(this, child));
                    }
                }
            }
        } 

        #endregion 

        public delegate void SetContextMenuHandler(ContextMenu menu);

        #region Fields

        private object _model;
        private String labelProperty_; // To cache the label property
        private String label_;
        private List<String> childrenProperties_; // To cache the children properties
        private SetContextMenuHandler setContextMenu_; // To set the context menu

        #endregion
    }
}
